#!/usr/bin/env python

import itertools
import optparse
import sys

import tables

import charmicat


h5types = {tables.description.Float32Col: 'H5T_NATIVE_FLOAT',
           tables.description.UInt8Col: 'H5T_NATIVE_UCHAR',
           tables.description.Int32Col: 'H5T_NATIVE_INT',
           tables.description.UInt32Col: 'H5T_NATIVE_UINT',
           tables.description.Int64Col: 'H5T_NATIVE_LONG',
           }

ctypes  = {tables.description.Float32Col: 'float',
           tables.description.Int32Col: 'int',
           tables.description.UInt8Col: 'unsigned char',
           tables.description.UInt32Col: 'unsigned int',
           tables.description.Int64Col: 'long',
           }

def groups_of_n(n, iterable, padvalue=None):
    return itertools.izip(*[itertools.chain(iterable, 
                                            itertools.repeat(padvalue, n-1))]*n)

def main():    
    '''
    pytpp_pytrow_to_struct.py [opts] module row_name

    Imports `module' and looks in `module.ntdef' for a row named `row_name',
    writing out a C++ header file with the equivalent pytpp row definition.
    '''

    op = optparse.OptionParser(usage=main.__doc__)

    op.add_option("--subset", dest='subset', default=None, 
                  help='ROW is a subset of output row; format output as such', 
                  metavar='ROW')

    opts, args = op.parse_args()

    if len(args) == 0:
        msg = "Please supply the name of an analysis package."
        raise charmicat.BadCommandLineError(msg, -1)

    module_name = args.pop(0)

    module = charmicat.safe_import(module_name)
    
    choices = module.ntdef.rowdefs.keys()
        
    if len(args) == 0:
        msg = "Please supply the name of a row in `{}.ntrow';"
        msg += " choices are: {}"
        raise charmicat.BadCommandLineError(msg.format(module_name, choices), 
                                            -2)
    row_name = args.pop(0)
    
    if row_name not in choices:
        msg = "Row `{0}' not available in `{1}.ntrow';".format(row_name, 
                                                               module_name)
        msg += " choices are: {0}".format(choices)
        raise charmicat.BadCommandLineError(msg, -3)

    if opts.subset and opts.subset not in choices:
        msg = "I forgot what the subset option is useful for"
        raise NotImplementedError(msg)
        msg = "Row `{0}' not available in `{1}.ntrow';".format(opts.subset, module_name)
        msg += " choices are: {0}".format(choices)
        raise charmicat.BadCommandLineError(msg, -3)
        
    rowdef = module.ntdef.rowdefs[row_name]
    fields = sorted(rowdef.keys())
    descrs = dict([(f, type(rowdef[f])) for f in fields])


    if opts.subset:
        msg = "I forgot what the subset option is useful for"
        raise NotImplementedError(msg)
#        subset_rows = sorted(module.ntdef.rowdefs[opts.subset].keys())
#        remaining_rows = sorted(set(module.ntdef.rowdefs[row_name].keys()) - set(subset_rows))
        
#        fields = subset_rows + remaining_rows

#    descrs = dict([(f, type(module.ntcolumn.allcols[f])) for f in fields])

    cpp_handle = "{0}_{1}row".format(module_name, row_name)

    fpout = open(cpp_handle + '.hpp', 'w')

    fpout.write('#ifndef {0}_\n#define {0}_\n\n'.format(
            fpout.name.replace('.', '_').upper()))
    fpout.write('#include <vector>\n\n')
    fpout.write('#include <hdf5.h>\n\n')
    fpout.write('struct {0} {{\n'.format(cpp_handle + '_t'))
    indent = "  "
    fpout.write(indent + 'public:\n')
    indent += "  "

    for f in fields:
        fpout.write(indent + '{0} {1};\n'.format(ctypes[descrs[f]], f))

    fpout.write('\n')

    fpout.write(indent + 'static std::vector<hid_t> field_types() {\n')
    
    indent += "  "
    fpout.write(indent + 'static std::vector<hid_t> types;\n\n')
    fpout.write(indent + 'if (types.size() == 0) {\n')

    indent += "  "

    for f in fields:
        fpout.write(indent + 
                    'types.push_back({0});\n'.format(h5types[descrs[f]]))

    indent = indent[:-2]
    fpout.write(indent + '}\n\n')
    
    fpout.write(indent + 'return types;\n')
    indent = indent[:-2]
    fpout.write(indent + '}\n\n')

    # Write field_names()
    fpout.write(indent + 'static std::vector<std::string> field_names() {\n')

    indent += "  "
    fpout.write(indent + 'static std::vector<std::string> fields;\n\n')
    fpout.write(indent + 'if (fields.size() == 0) {\n')
    
    indent += "  "
    for f in fields:
        fpout.write(indent + 'fields.push_back("{0}");\n'.format(f))

    indent = indent[:-2]
    fpout.write(indent + '}\n\n')
    
    fpout.write(indent + 'return fields;\n')
    indent = indent[:-2]
    fpout.write(indent + '}\n\n')
    
    # Write h5_type()
    fpout.write(indent + 'static hid_t h5_type() {\n')
    indent += "  "
    fpout.write(indent + 'static hid_t type_id = 0;\n\n')
    fpout.write(indent + 'if (type_id == 0) {\n')

    indent += "  "
    fpout.write(
        indent + 'type_id = H5Tcreate(H5T_COMPOUND, sizeof({0}));\n\n'.format(
            cpp_handle + '_t'))
    for f in fields:
        fpout.write(
            indent + \
                'H5Tinsert(type_id, "{0}", HOFFSET({1}, {0}), {2});\n'.format(
                    f, cpp_handle + '_t', h5types[descrs[f]]))
        
    indent = indent[:-2]
    fpout.write(indent + '}\n\n')
    
    fpout.write(indent + 'return type_id;\n')
    indent = indent[:-2]
    fpout.write(indent + '}\n\n')   
    
    # Write hbook_defstr()
    fpout.write(indent + 'static const char* hbook_defstr() {\n')
    indent += "  "

    buf = []
    for g in groups_of_n(3, fields, padvalue=''):
        buf.append(' '.join([i for i in g if i]))

    hbindent = indent + " "*8
    hbook_defstr = '"' + '\\\n{0}'.format(hbindent).join(buf) + '";' 
          
    fpout.write(indent + 'return {0}\n'.format(hbook_defstr))
    indent = indent[:-2]
    fpout.write(indent + '}\n\n')   

    indent = indent[:-2]
    fpout.write('};\n\n')

    indent = indent[:-2]
    fpout.write(indent + "{0}_t {0}_default = {{\n".format(cpp_handle))
    indent += "  "

    fieldstr = ', '.join(map(str, [rowdef[f].dflt for f in fields]))
    tokens = fieldstr.split()
    for i,f in enumerate(fields):
        tokens[i] = indent + tokens[i] + " //" + f

    fpout.write('\n'.join(tokens))
    fpout.write('\n')
    indent = indent[:-2]
    fpout.write("};\n\n")

    fpout.write('#endif //{0}_\n'.format(fpout.name.replace('.', '_').upper()))

    fpout.close()

    return 0
    


if __name__ == "__main__":
    try:
        sys.exit(main())
    except charmicat.BadCommandLineError, e:
        sys.stderr.write(str(e) + "\n")
        sys.stderr.write("See `pytpp_pytrow_to_struct.py -h' for more.\n")
        sys.stderr.flush()
        sys.exit(e.retcode)




